package org.springframework.howelly.services;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.ParseException;
import java.util.Collections;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.springframework.howelly.helpers.SearchCriteria;
import org.springframework.howelly.model.Gasto;
import org.springframework.howelly.model.Ingreso;
import org.springframework.howelly.model.Obra;
import org.springframework.howelly.model.User;
import org.springframework.howelly.utils.Constants;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.webflow.execution.RequestContext;

@Service("gastoService")
@Repository
public class JpaGastoService implements GastoService {

    private EntityManager em;

    @PersistenceContext
    public void setEntityManager(EntityManager em) {
	this.em = em;
    }

    @Transactional(readOnly = true)
    @SuppressWarnings("unchecked")
    public List<Gasto> findGastos(String username, long obraId, RequestContext context) {
	if (username != null) {
	    List<Gasto> result = em.createQuery(
		    "select g from Gasto g  where g.obra.id =:obraId order by g.diaEfectuado desc").setParameter(
		    "obraId", obraId).getResultList();
	    return result;
	} else {
	    return null;
	}
    }

    @Transactional(readOnly = true)
    @SuppressWarnings("unchecked")
    public List<Gasto> filtrarGastos(Gasto gasto, long obraId, RequestContext context) throws ParseException {
	List<Gasto> results = em.createQuery(createSearchQuery(gasto)).setParameter("obraId", obraId).getResultList();
	return results;
    }

    private String createSearchQuery(Gasto gasto) throws ParseException {
	String sql = "select g from Gasto g where g.obra.id =:obraId ";

	if (!gasto.getName().equals("")) {
	    sql = sql + "and g.name like '%" + gasto.getName() + "%'";
	}
	if (!gasto.getDiaEfectuadoString().equals("")) {
	    sql = sql + "and g.diaEfectuadoString = '" + gasto.getDiaEfectuadoString() + "'";
	}
	if (gasto.getPrice().intValue() != 0) {
	    sql = sql + "and g.price = '" + gasto.getPrice().toString() + "'";
	}
	if (!gasto.getCategoriaName().equals("")) {
	    sql = sql + "and g.categoriaName like '%" + gasto.getCategoriaName() + "%'";
	}
	if (!gasto.getFactura().equals("")) {
	    sql = sql + "and g.factura like '%" + gasto.getFactura() + "%'";
	}
	if (!gasto.getExpenseType().equals("")) {
	    sql = sql + "and expenseType like '%" + gasto.getExpenseType() + "%'";
	}

	return sql;
    }

    @Transactional(readOnly = true)
    public Gasto createGasto(Long obraId, String username) {
	Obra obra = em.find(Obra.class, obraId);
	User user = findUser(username);
	return new Gasto(obra, user);
    }

    @Transactional(readOnly = true)
    public Gasto createIngreso(Long obraId, String username) {
	Obra obra = em.find(Obra.class, obraId);
	User user = findUser(username);
	return new Ingreso(obra, user);
    }

    public User findUser(String username) {
	return (User) em.createQuery("select u from User u where u.username = :username").setParameter("username",
		username).getSingleResult();
    }

    public Gasto calculateTotalsAndSave(Gasto gasto, long obraId, RequestContext context) {
	Obra obra = em.find(Obra.class, obraId);
	List<Gasto> finanzas = findGastos("", obraId, context);
	BigDecimal saldoParcial = BigDecimal.ZERO;
	finanzas.add(gasto);
	Collections.sort(finanzas, Gasto.ByDate);
	for (Gasto myFinanza : finanzas) {
	    if (myFinanza.getExpenseType().equals(Constants.GASTO)) {
		saldoParcial = saldoParcial.subtract(myFinanza.getPrice());
	    } else
		saldoParcial = saldoParcial.add(myFinanza.getPrice());
	}
	obra.setSaldo(saldoParcial);
	gasto.calculateSaldosInGasto(obra, context);
	overrideCuitByName(gasto, obraId);
	em.merge(gasto);
	return gasto;
    }

    private void overrideCuitByName(Gasto gasto, long obraId) {
	if (gasto.isOverrideCuit()) {
	    List<Gasto> gastos = findGastosByName(gasto.getName(), obraId);
	    for (Gasto gasto2 : gastos) {
		gasto2.setCuit(gasto.getCuit());
		em.persist(gasto2);
	    }

	}
    }

    @Transactional
    public boolean deleteGasto(Gasto gastoFrontEnd) {
	Gasto gasto = em.find(Gasto.class, gastoFrontEnd.getId());
	Obra obra = gasto.getObra();
	BigDecimal saldo = BigDecimal.ZERO;
	if (gasto.getExpenseType().equals("Ingreso")) {
	    saldo = obra.getSaldo().subtract(gasto.getPrice()).setScale(2, RoundingMode.HALF_DOWN);
	} else if (gasto.getExpenseType().equals("Gasto"))
	    saldo = obra.getSaldo().add(gasto.getPrice()).setScale(2, RoundingMode.HALF_DOWN);

	obra.setSaldo(saldo);
	if (gasto != null) {
	    em.remove(gasto);
	    em.flush();
	    return true;
	}
	return false;
    }

    public List<String> getNameOptions(String query) {

	return em.createQuery("select distinct g.name from Gasto g  where  g.name like '%" + query + "%'")
		.setMaxResults(10).getResultList();
    }

    @SuppressWarnings("unchecked")
    @Transactional
    public List<Gasto> findGastosByFactura(SearchCriteria criteria) {
	// make sure that is going to the DB for real data
	em.flush();
	em.clear();
	List<Gasto> results = em.createQuery(
		"from Gasto where expenseType = 'Gasto' and factura = 'Factura A' and diaEfectuado >= '"
			+ criteria.getFromString() + "' and diaEfectuado <= '" + criteria.getToString() + "'")
		.getResultList();

	return results;
    }

    @Transactional(readOnly = true)
    public List<Gasto> findGastosByName(String name, long obraId) {
	return em.createQuery("select g from Gasto g  where g.obra.id =:obraId and g.name = :name").setParameter(
		"obraId", obraId).setParameter("name", name).getResultList();
    }

    @Transactional
    public String getCuitByName(String name) {
	List<String> cuits = em.createQuery("select g.cuit from Gasto g  where  g.name = '" + name + "'")
		.getResultList();
	return cuits.isEmpty() ? "" : cuits.get(0);
    }

}
